package net.stuffrepos.bricknetcenter.database.schema;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.Transient;
import net.stuffrepos.bricknetcenter.http.HttpParser;
import net.stuffrepos.util.cache.CacheableValue;
import org.hibernate.annotations.GenericGenerator;

/**
 *
 * @author Eduardo H. Bogoni <eduardobogoni@gmail.com>
 */
@Entity
@Table(name = "http_requests")
public class HttpRequest implements Serializable {

    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    @Column(length = 32)
    private String id;
    @Column(length = 4096, unique = true)
    private String urlSpecification;
    @Column(length = 4096)
    private String refererSpecification;
    @Column(length = 4096)
    private String parserClass;
    @Transient
    private HttpRequestImpl httpRequest = new HttpRequestImpl();
    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private Status status;
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Date createdOn;
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Date modifiedOn;
    @Column(nullable = false)
    private int priority = 0;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "http_request_id", referencedColumnName = "id")
    private List<HttpRequestPostParameter> postParameters = new LinkedList<HttpRequestPostParameter>();
    @Column
    @Enumerated(EnumType.STRING)
    private net.stuffrepos.bricknetcenter.http.HttpRequest.Method method;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "http_request_id", referencedColumnName = "id")
    private List<HttpRequestParserArgument> parseArguments = new LinkedList<HttpRequestParserArgument>();

    public String getUrlSpecification() {
        return urlSpecification;
    }

    public void setUrlSpecification(String urlSpecification) {
        this.urlSpecification = urlSpecification;
        httpRequest.url.clear();
    }

    public String getParserClass() {
        return parserClass;
    }

    public void setParserClass(String parserClass) {
        this.parserClass = parserClass;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    public Date getCreatedOn() {
        return createdOn;
    }

    public void setCreatedOn(Date createdOn) {
        this.createdOn = createdOn;
    }

    public Date getModifiedOn() {
        return modifiedOn;
    }

    public void setModifiedOn(Date modifiedOn) {
        this.modifiedOn = modifiedOn;
    }

    /**
     * @return the refererSpecification
     */
    public String getRefererSpecification() {
        return refererSpecification;
    }

    /**
     * @param refererSpecification the refererSpecification to set
     */
    public void setRefererSpecification(String refererSpecification) {
        this.refererSpecification = refererSpecification;
        httpRequest.referer.clear();
    }

    public void setHttpRequest(net.stuffrepos.bricknetcenter.http.HttpRequest request) {
        this.urlSpecification = request.getUrl().toString();
        if (request.getReferer() == null) {
            this.refererSpecification = null;
        } else {
            this.refererSpecification = request.getReferer().toString();
        }

        this.method = request.getMethod();
        this.postParameters = new LinkedList<HttpRequestPostParameter>();
        for (net.stuffrepos.bricknetcenter.http.HttpRequest.PostParameter postParameter : request.getPostParameters()) {
            HttpRequestPostParameter persistencePostParameter = new HttpRequestPostParameter();
            persistencePostParameter.setName(postParameter.getName());
            persistencePostParameter.setValue(postParameter.getValue());
            persistencePostParameter.setHttpRequest(this);
            this.postParameters.add(persistencePostParameter);
        }

        this.parserClass = request.getParserClass().getName();
        this.parseArguments = new LinkedList<HttpRequestParserArgument>();
        for (int callOrder = 0; callOrder < request.getParserArguments().length; ++callOrder) {
            HttpRequestParserArgument persistentParserArgument = new HttpRequestParserArgument();
            persistentParserArgument.setHttpRequest(this);
            persistentParserArgument.setCallPosition(callOrder);
            persistentParserArgument.setValueType(
                    HttpParser.ValueType.byObject(
                    request.getParserArguments()[callOrder]));
            persistentParserArgument.setValue(
                    persistentParserArgument.getValueType().objectToString(
                    request.getParserArguments()[callOrder]));
            this.parseArguments.add(persistentParserArgument);
        }

        this.priority = request.getPriority();
    }

    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    public List<HttpRequestPostParameter> getPostParameters() {
        return postParameters;
    }

    public void setPostParameters(List<HttpRequestPostParameter> postParameters) {
        this.postParameters = postParameters;
    }

    public net.stuffrepos.bricknetcenter.http.HttpRequest.Method getMethod() {
        return method;
    }

    public void setMethod(net.stuffrepos.bricknetcenter.http.HttpRequest.Method method) {
        this.method = method;
    }

    public List<HttpRequestParserArgument> getParseArguments() {
        return parseArguments;
    }

    public void setParseArguments(List<HttpRequestParserArgument> parseArguments) {
        this.parseArguments = parseArguments;
    }

    private class HttpRequestImpl implements net.stuffrepos.bricknetcenter.http.HttpRequest {

        private CacheableValue<URL> url = new CacheableValue<URL>() {

            @Override
            protected URL calculate() {
                try {
                    return new URL(urlSpecification);
                } catch (MalformedURLException ex) {
                    throw new RuntimeException(ex);
                }
            }
        };
        private CacheableValue<URL> referer = new CacheableValue<URL>() {

            @Override
            protected URL calculate() {
                if (refererSpecification == null) {
                    return null;
                } else {
                    try {
                        return new URL(refererSpecification);
                    } catch (MalformedURLException ex) {
                        throw new RuntimeException(ex);
                    }
                }

            }
        };

        @Override
        public URL getUrl() {
            return url.getValue();
        }

        @Override
        public Class<? extends HttpParser> getParserClass() {
            try {
                return (Class<? extends HttpParser>) Class.forName(parserClass);
            } catch (ClassNotFoundException ex) {
                throw new RuntimeException(ex);
            }
        }

        @Override
        public URL getReferer() {
            return referer.getValue();
        }

        @Override
        public int getPriority() {
            return priority;
        }

        @Override
        public PostParameter[] getPostParameters() {
            LinkedList<PostParameter> result = new LinkedList<PostParameter>();
            for (HttpRequestPostParameter parameter : HttpRequest.this.getPostParameters()) {
                result.add((new PostParameter(parameter.getName(), parameter.getValue())));
            }
            return result.toArray(new PostParameter[0]);
        }

        @Override
        public Method getMethod() {
            return HttpRequest.this.getMethod();
        }                

        @Override
        public Object[] getParserArguments() {
            List<Object> args = new LinkedList<Object>();
            for (HttpRequestParserArgument parseArgument : parseArguments) {
                args.add(parseArgument.getValueObject());
            }
            return args.toArray();
        }
    }

    public net.stuffrepos.bricknetcenter.http.HttpRequest getHttpRequest() {
        return httpRequest;
    }

    public static enum Status {

        Completed,
        New,
        Requested,
        Failed
    }
}
